//      priv_escritor.c
// 		Sistemas Operacionais 1  da Universidade Federal de Goias
//		Problema dos leitores-escritores como privilegio para os escritores
//      Autor: Arthur Henrique Guimaraes de Oliveira
//		Autor: Danilo Inácio de Souza Resende
//      
//      Este programa esta Disponivel sobre a Lincença GNU General Public License v3
//		no seguinte endereço: https://code.google.com/p/so-lving/     

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "common.h"
#include "priv_escritor.h"

// ------- Variáveis -------- //
dados_cmp cmp;
FILE *log_file;

int main(int argc, char* argv[]){

    // Inicializa variáveis
    srand( time(NULL) );
    log_file = fopen("log.txt", "w");       // arquivo de log
    pthread_t  esc[T_LIMIT], leit[T_LIMIT]; // threads
    int        qt_esc=0, qt_leit=0;         // contador p/ as thread
    targs      cur_targs[T_LIMIT];          // argumentos das threads
    pthread_attr_t attr;                    // para tornar as thread joinable
    void       *status;                     // para a função pthread_join
    int        rc;                          // para a função pthread_join
    cmp.estoque_index = 0;                  // indice do estoque

    int i;
    for(i=0; i<T_LIMIT; i++)
        cmp.estoque[i] = ' ';

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    // inicializa semafaros
    sem_init(&cmp.mut_escritores, 0, 1);
    sem_init(&cmp.mut_leitores  , 0, 1);
    sem_init(&cmp.block_escr, 0, 1);
    sem_init(&cmp.block_leit, 0, 1);
    sem_init(&cmp.opr_leitor, 0, 1);
    sem_init(&cmp.mut_print , 0, 1);

    // threads
    if(argc > 1){
        if(strcmp(argv[1], "--auto") == 0){
            int rnd_t;

            for(i=0; i<T_LIMIT; i++){
                rnd_t = rand() % 2;
                cur_targs[i].sleept = rand() % 11;
                
                switch(rnd_t){
                    case GERENTE:
                        cur_targs[i].id     = qt_leit;
                        cur_targs[i].type   = GERENTE;
                        printf("Leitor   %d foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].id, cur_targs[i].sleept);
                        pthread_create(&leit[qt_leit], &attr, leitor, (void*) &cur_targs[i]);
                        qt_leit++;
                        break;

                    case PRODUTOR:
                        cur_targs[i].id     = qt_esc;
                        cur_targs[i].type   = PRODUTOR;
                        printf("Escritor %d foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].id, cur_targs[i].sleept);
                        pthread_create(&esc[qt_esc], &attr, escritor, (void*) &cur_targs[i]);
                        qt_esc++;
                        break;
                }
            }
        }
        // Se o argumento passado for errado
        else {
            printf("%s", how_to_use());
        }
    }
    // Caso o usuário não passe parâmetros ele terá controle sobre a criação
    // das threads
    else {
        int line_width = 20;
        char line[line_width + 1], *token, args[line_width][line_width];
        int error, nargs;

        // Mensagem básica
        printf("Abra um novo terminal e execute \"$tail -f log.txt\" para ver o log das ações\n");
        for(i=0; i<T_LIMIT; i++){
            error = nargs = 0;
            args[0][0] = args[1][0] = '\0';

            printf("%s", menu());
            fgets(line, line_width, stdin);
            line[strlen(line) - 1] = '\0';

            if(strcmp(line, "exit") == 0){
                break;
            }

            token = strtok(line, " ");
            while(token != NULL){
                strcpy(args[nargs++], token);
                token = strtok(NULL, " ");
            }
            
            cur_targs[i].id = i;
            if(strcmp(args[0], "p") == 0) {
                cur_targs[i].type = PRODUTOR;
            }
            else if(strcmp(args[0], "g") == 0) {
                cur_targs[i].type = GERENTE;
            }
            else {
                error = 1;
            }

            if(args[1][0] != '\0'){
                cur_targs[i].sleept = atoi(args[1]);
                if(cur_targs[i].sleept < 0 || cur_targs[i].sleept > 10) {
                    error = 1;
                }

            }
            else {
                cur_targs[i].sleept = rand() % 11;
            }

            if(error) {
                printf("Erro, use o programa segundo o menu:");
                continue;
            }

            switch(cur_targs[i].type){
                case GERENTE:
                    printf("Um leitor foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].sleept);
                    pthread_create(&leit[qt_leit], &attr, leitor, (void*) &cur_targs[i]);
                    qt_leit++;
                    break;

                case PRODUTOR:
                    printf("Um escritor foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].sleept);
                    pthread_create(&esc[qt_esc], &attr, escritor, (void*) &cur_targs[i]);
                    qt_esc++;
                    break;
            }
            fflush(stdin);

        }

    }
    printf("saindo..\n");
    // Aguarda o termino das threads
    pthread_attr_destroy(&attr);
    for(i=0; i<qt_esc || i<qt_leit; i++){
        if(i<qt_leit){
            rc = pthread_join(leit[i], &status);
            if (rc) {
                printf("ERROR; return code from pthread_join() is %d\n", rc);
                exit(-1);
            }
        }
        if(i<qt_esc){
            rc = pthread_join(esc[i], &status);
            if (rc) {
                printf("ERROR; return code from pthread_join() is %d\n", rc);
                exit(-1);
            }
        }
    }
    // fecha o arquivo
    fprintf(log_file, "Fim da execução\n");
    fclose(log_file);
    return 0;


    return 0;
}

void *leitor(void *arg){
    targs* struct_c = (targs*) arg;
    
    msg_ini(struct_c, log_file);

    sem_wait(&cmp.opr_leitor);
    sem_wait(&cmp.block_leit);

    sem_wait(&cmp.mut_leitores);
    cmp.qt_leitores++;
    if(cmp.qt_leitores == 1){
        sem_wait(&cmp.block_escr);
    }
    sem_post(&cmp.mut_leitores);

    sem_post(&cmp.block_leit);
    sem_post(&cmp.opr_leitor);

    acessa_area_compartilhada(struct_c, log_file, cmp.estoque, cmp.estoque_index);

    sem_wait(&cmp.mut_leitores);
    cmp.qt_leitores--;
    if(cmp.qt_leitores == 0){
        sem_post(&cmp.block_escr);
    }
    sem_post(&cmp.mut_leitores);

    return NULL;
}

void *escritor(void *arg){
    targs* struct_c = (targs*) arg;

    msg_ini(struct_c, log_file);

    sem_wait(&cmp.mut_escritores);
    cmp.qt_escritores++;
    if(cmp.qt_escritores == 1){
        sem_wait(&cmp.block_leit);
    }
    sem_post(&cmp.mut_escritores);

    sem_wait(&cmp.block_escr);
    acessa_area_compartilhada(struct_c, log_file, cmp.estoque, &cmp.estoque_index);
    sem_post(&cmp.block_escr);

    sem_wait(&cmp.mut_escritores);
    cmp.qt_escritores--;
    if(cmp.qt_escritores == 0){
        sem_post(&cmp.block_leit);
    }
    sem_post(&cmp.mut_escritores);

    return NULL;
}


